﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Asol.Reporting.Support.Data
{
    #region Table : Tabulka s daty
    /// <summary>
    /// Tabulka s daty.
    /// Tabulku je možno vytvářet ručně (přidávat data do Columns i do Rows),
    /// nebo ji načítat z databáze napřímo pomocí readeru (System.Data.Common.DbDataReader),
    /// nebo ji vytvořit z načtené tabulky (System.Data.DataTable), pomocí statických metod Table.CreateFromReader() nebo Table.CreateFromDataTable().
    /// </summary>
    public class Table : IDisposable
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        public Table()
        {
            this.Columns = new ColumnCollection(this);
            this.Rows = new RowCollection(this);
        }
        /// <summary>
        /// Vrátí novou prázdnou tabulku pro data
        /// </summary>
        public static Table Empty { get { return new Table(); } }
        /// <summary>
        /// Vrátí tabulku vytvořenou z dodané definice sloupců.
        /// Ta má tvar jednoho stringu s oddělovači.
        /// Pokud jsou použity pouze čárky, pak jde o "Green" notaci (name1,type1,name2,type2,...).
        /// Pokud jsou použity čárky a středníky, pak jde o Asol notaci (name1,type1,edit1;name2,type2,edit2;...).
        /// Hodnota "edit" popisuje editační styl (maska nebo položky).
        /// </summary>
        /// <param name="columnDefinition"></param>
        /// <returns></returns>
        public static Table CreateFromDefinition(string columnDefinition)
        {
            Table table = new Table();
            if (String.IsNullOrEmpty(columnDefinition)) return table;
            bool containG = columnDefinition.Contains(',');
            bool containA = columnDefinition.Contains(';');
            if (containG && containA)
                table.Columns = ColumnCollection.CreateFromDefinitionAsol(table, columnDefinition, ';', ',');
            else if (containG)
                table.Columns = ColumnCollection.CreateFromDefinitionGreen(table, columnDefinition, ',');
            else if (containA)
                table.Columns = ColumnCollection.CreateFromDefinitionGreen(table, columnDefinition, ';');
            return table;
        }

        #endregion
        #region Columns
        /// <summary>
        /// Sloupce tabulky
        /// </summary>
        public ColumnCollection Columns { get; private set; }
        /// <summary>
        /// Počet sloupců
        /// </summary>
        public int ColumnCount { get { return this.Columns.Count; } }
        /// <summary>
        /// Do tabulky přidá sloupec beze jména
        /// </summary>
        /// <returns></returns>
        public Column AddColumn()
        {
            Column column = new Column();
            this.Columns.Add(column);
            return column;
        }
        /// <summary>
        /// Do tabulky přidá sloupec daného jména
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Column AddColumn(string name)
        {
            Column column = new Column(name);
            this.Columns.Add(column);
            return column;
        }
        /// <summary>
        /// Do tabulky přidá sloupec daného jména a typu
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Column AddColumn(string name, Type dataType)
        {
            Column column = new Column(name, dataType);
            this.Columns.Add(column);
            return column;
        }
        #endregion
        #region Rows
        /// <summary>
        /// Řádky tabulky
        /// </summary>
        public RowCollection Rows { get; private set; }
        /// <summary>
        /// Počet řádků
        /// </summary>
        public int RowCount { get { return this.Rows.Count; } }
        /// <summary>
        /// Do tabulky přidá řádek a vrátí jej
        /// </summary>
        /// <returns></returns>
        public Row AddRow()
        {
            Row row = new Row();
            this.Rows.Add(row);
            return row;
        }
        /// <summary>
        /// Do tabulky přidá řádek a vrátí jej
        /// </summary>
        /// <returns></returns>
        public Row AddRow(params object[] values)
        {
            Row row = new Row();
            this.Rows.Add(row);             // Tady proběhne zařazení řádku do kolekce řádků, tím dostane i referenci na tabulku a sloupce = počet sloupců
            row.FillCellsFrom(values);      // Tady se využije počet sloupců a jejich datové typy, takže vstupující hodnoty budou patřičně korigovány.
            return row;
        }
        #endregion
        #region Načítání ze streamu (DbDataReader), z tabulky (DataTable)
        /// <summary>
        /// Z dodaného streamu vrátí tabulku naplněnou daty
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static Table CreateFromReader(System.Data.Common.DbDataReader reader)
        {
            Table table = new Table();
            table.Columns.FillFromReader(reader);
            table.Rows.FillFromReader(reader);
            return table;
        }
        /// <summary>
        /// Z dodané tabulky vrátí tabulku naplněnou daty
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static Table CreateFromDataTable(System.Data.DataTable dataTable)
        {
            Table table = new Table();
            table.Columns.FillFromDataTable(dataTable);
            table.Rows.FillFromDataTable(dataTable);
            return table;
        }
        #endregion
        #region Propojení tabulky (this) s vizuálním Gridem a podpora vizualizace a editace
        /// <summary>
        /// Příznak true, pokud k této tabulce existuje vizuální DataGrid.
        /// Pak se automaticky propojují vlastnosti této tabulky do jejího gridu.
        /// </summary>
        public bool HasGrid { get { return (this._Grid != null); } }
        /// <summary>
        /// Objekt sloužící k vizualizaci Gridu
        /// </summary>
        public Components.Grid Grid
        {
            get { return this._Grid; }
            set
            {
                if (this._Grid != null)
                    this._DataGridUnlink(this._Grid);
                this._Grid = value;
                if (this._Grid != null)
                    this._DataGridLink(this._Grid);
            }
        } private Components.Grid _Grid;
        /// <summary>
        /// Propojí se s daným gridem
        /// </summary>
        /// <param name="dataGrid"></param>
        private void _DataGridLink(Components.Grid dataGrid)
        {

        }
        /// <summary>
        /// Odpojí se od daného gridu
        /// </summary>
        /// <param name="dataGrid"></param>
        private void _DataGridUnlink(Components.Grid dataGrid)
        {

        }
        /// <summary>
        /// Vrátí režim slučování buněk pro danou buňku.
        /// Podporuje možnost SimpleDataColumn.ShowOnlyDifferentValues.
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public CellJoinMode DetectCellJoin(int rowIndex, int columnIndex, IDataSource dataSource)
        {
            if (rowIndex >= 0 && rowIndex < this.RowCount && columnIndex >= 0 && columnIndex < this.ColumnCount)
            {
                Column column = this.Columns[columnIndex];      // V tomto sloupci určujeme JoinMode
                if (column.GridShowOnlyDifferentValues)
                {
                    int detectInColumn = (column.GridDifferentValuesDetectInColumnIndex.HasValue ? column.GridDifferentValuesDetectInColumnIndex.Value : column.ColumnIndex);
                    bool isFirst = (rowIndex == 0 || this._DetectCellJoinDifferentValue(rowIndex, rowIndex - 1, detectInColumn, dataSource));
                    bool isLast = (rowIndex == (this.RowCount - 1) || this._DetectCellJoinDifferentValue(rowIndex, rowIndex + 1, detectInColumn, dataSource));
                    if (isFirst && isLast)
                        return CellJoinMode.SingleCell;
                    if (isFirst)
                        return CellJoinMode.FirstCell;
                    if (isLast)
                        return CellJoinMode.LastCell;
                    return CellJoinMode.InnerCell;
                }
            }
            return CellJoinMode.None;
        }
        /// <summary>
        /// Vrací true, pokud data v daných řádcích daného sloupce jsou ODLIŠNÁ.
        /// </summary>
        /// <param name="rowIndex1"></param>
        /// <param name="rowIndex2"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        private bool _DetectCellJoinDifferentValue(int rowIndex1, int rowIndex2, int columnIndex, IDataSource dataSource)
        {
            Column column = this.Columns[columnIndex];
            string value1 = this.GetDisplayValue(rowIndex1, columnIndex, dataSource);
            string value2 = this.GetDisplayValue(rowIndex2, columnIndex, dataSource);
            return (!String.Equals(value1, value2, StringComparison.InvariantCulture));
        }
        /// <summary>
        /// Vrátí řetězcovou hodnotu z tabulky, pro zobrazení (korektně zformátovaná)
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public string GetDisplayValue(int rowIndex, int columnIndex, IDataSource dataSource)
        {
            Row row = ((rowIndex >= 0 && rowIndex < this.RowCount) ? this.Rows[rowIndex] : null);
            Column column = ((columnIndex >= 0 && columnIndex < this.ColumnCount) ? this.Columns[columnIndex] : null);
            return this.GetDisplayValue(row, column, dataSource);
        }
        /// <summary>
        /// Vrátí řetězcovou hodnotu z tabulky, pro zobrazení (korektně zformátovaná).
        /// Nevrací null.
        /// </summary>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public string GetDisplayValue(Row row, Column column, IDataSource dataSource)
        {
            if (row == null || column == null) return "";
            ValueData value = row[column];
            FormatStyle formatStyle = this.GetFormatStyle(row, column);
            return formatStyle.ConvertValueToText(value);
        }
        /// <summary>
        /// Vrátí formátovací objekt pro defaultní formátování obecné hodnoty na text.
        /// Nevrací null.
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public FormatStyle GetFormatStyle(int rowIndex, int columnIndex)
        {
            Row row = ((rowIndex >= 0 && rowIndex < this.RowCount) ? this.Rows[rowIndex] : null);
            Column column = ((columnIndex >= 0 && columnIndex < this.ColumnCount) ? this.Columns[columnIndex] : null);
            return this.GetFormatStyle(row, column);
        }
        /// <summary>
        /// Vrátí formátovací objekt pro defaultní formátování obecné hodnoty na text.
        /// Nevrací null.
        /// </summary>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public FormatStyle GetFormatStyle(Row row, Column column)
        {
            FormatStyle formatStyle = null;
            if (row != null && column != null)
                formatStyle = row.GetCellFormatStyle(column.ColumnIndex);
            if (formatStyle == null && column != null)
                formatStyle = column.GridFormat;
            if (formatStyle == null)
                formatStyle = Assistant.DefaultFormatStyle;
            return formatStyle;
        }
        /// <summary>
        /// Vrací hodnotu true / false, která určuje zda danou buňku je možno editovat.
        /// Výchozí hodnoty jsou true, všechny entity musí být true aby buňka byla editovatelná.
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public bool IsEnabled(int rowIndex, int columnIndex)
        {
            Row row = ((rowIndex >= 0 && rowIndex < this.RowCount) ? this.Rows[rowIndex] : null);
            Column column = ((columnIndex >= 0 && columnIndex < this.ColumnCount) ? this.Columns[columnIndex] : null);
            return this.IsEnabled(row, column);
        }
        /// <summary>
        /// Vrací hodnotu true / false, která určuje zda danou buňku je možno editovat.
        /// Výchozí hodnoty jsou true, všechny entity musí být true aby buňka byla editovatelná.
        /// </summary>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public bool IsEnabled(Row row, Column column)
        {
            // Všechno musí být Enabled:
            bool enabled = this.Enabled;

            if (enabled && row != null)
                // Řádek:
                enabled = row.Enabled;

            if (enabled && column != null)
                // Sloupec:
                enabled = column.Enabled;

            if (enabled && row != null && column != null)
                // Buňka řádku:
                enabled = row.GetCellEnabled(column.ColumnIndex);

            return enabled;
        }
        /// <summary>
        /// Vrátí styl zobrazení pro daný stav buňky a její pozici
        /// </summary>
        /// <param name="state"></param>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public Components.GridStyleItem GetStyle(Components.GridState state, int rowIndex, int columnIndex)
        {
            Row row = ((rowIndex >= 0 && rowIndex < this.RowCount) ? this.Rows[rowIndex] : null);
            Column column = ((columnIndex >= 0 && columnIndex < this.ColumnCount) ? this.Columns[columnIndex] : null);
            return GetStyle(state, row, column);
        }
        /// <summary>
        /// Vrátí styl zobrazení pro daný stav buňky a její pozici
        /// </summary>
        /// <param name="state"></param>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public Components.GridStyleItem GetStyle(Components.GridState state, Row row, Column column)
        {
            Components.GridStyle style = null;
            Components.GridStyleItem styleOne = null;
            // Priorita: buňka - řádek - sloupec - tabulka (zde se může v případě potřeby vytvořit i defaultní styl):
            if (row != null)
            {   // Buňka řádku:
                if (column != null)
                {
                    style = row.GetCellGridStyle(column.ColumnIndex);
                    if (style != null)
                        styleOne = style[state];
                }

                // Řádek:
                if (styleOne == null)
                {
                    style = row.GridStyle;
                    if (style != null)
                        styleOne = style[state];
                }
            }

            // Sloupec:
            if (styleOne == null && column != null)
            {
                style = column.GridStyle;
                if (style != null)
                    styleOne = style[state];
            }

            // Tabulka, explicitní styl:
            if (styleOne == null)
            {
                style = this.GridStyle;
                if (style != null)
                    styleOne = style[state];
            }

            // Tabulka, defaultní styl:
            if (styleOne == null)
                styleOne = this.GridStyleDefault[state];

            return styleOne;
        }
        /// <summary>
        /// Základní grafický styl pro tabulku = pro její prvky, které nemají svůj vlastní styl.
        /// Tato instance může být NULL, anebo může obsahovat objekt, který nepokrývá všechny stavy (GridState).
        /// </summary>
        public Components.GridStyle GridStyle { get; set; }
        /// <summary>
        /// Defaultní grafický styl, který obsahuje data pro všechny existující stavy.
        /// get zajistí on-demand vytvoření defaultního stylu při prvním přístupu.
        /// </summary>
        public Components.GridStyle GridStyleDefault
        {
            get
            {
                if (this._GridStyleDefault == null)
                    this._GridStyleDefault = Components.GridStyle.Default;
                return this._GridStyleDefault;
            }
        }
        private Components.GridStyle _GridStyleDefault;
        /// <summary>
        /// Příznak, že data tabulky mohou být editovatelná. Default = true.
        /// </summary>
        public bool Enabled { get { return !this._Disabled; } set { this._Disabled = !value; } } private bool _Disabled = false;
        /// <summary>
        /// Zajistí odeslání notifikace o změně hodnoty Column.Grid* do Gridu, navázaného na tabulce.
        /// </summary>
        /// <param name="column"></param>
        /// <param name="propertyName"></param>
        internal void NotifyGridColumnChange(Column column, string propertyName)
        {
            if (this._Grid != null)
                this._Grid.NotifyGridColumnChange(column, propertyName);
        }
        #endregion
        #region Sada konverzí hodnoty z netypového objektu na generický typový
        /// <summary>
        /// Vrátí typovou hodnotu z dodaného řádku a sloupce. Namísto DbNull vrací default daného typu.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="row"></param>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public static T Get<T>(System.Data.DataRow row, string columnName)
        {
            if (row == null) return default(T);
            if (!row.Table.Columns.Contains(columnName)) return default(T);
            if (row.IsNull(columnName)) return default(T);
            return (T)row[columnName];
        }
        /// <summary>
        /// Vrátí typovou hodnotu z dodaného řádku a sloupce. Namísto DbNull vrací default daného typu.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="row"></param>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public static T Get<T>(Data.Row row, string columnName)
        {
            if (row == null) return default(T);
            if (!row.Table.Columns.ContainsName(columnName)) return default(T);
            object value = row.Cells[row.Table.Columns[columnName].ColumnIndex];
            if (value == null) return default(T);
            return (T)value;
        }
        #endregion
        #region IDisposable
        void IDisposable.Dispose()
        {
            this.Rows.Clear();
            this.Columns.Clear();
        }
        #endregion
    }
    #endregion
    #region ColumnCollection + Column
    #region ColumnCollection : Kolekce sloupců tabulky
    /// <summary>
    /// Kolekce sloupců tabulky
    /// </summary>
    public class ColumnCollection : Collection<Column>
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        public ColumnCollection()
            : base(StringComparison.Ordinal)
        {
            this.Table = null;
            this._Initialize();
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="owner"></param>
        internal ColumnCollection(Table owner)
            : base(StringComparison.Ordinal)
        {
            this.Table = owner;
            this._Initialize();
        }
        /// <summary>
        /// Vytvoří a vrátí kolekci sloupců z Green notace (name1,type1,name2,type2,...).
        /// </summary>
        /// <param name="table"></param>
        /// <param name="columnDefinition"></param>
        /// <param name="delimiter"></param>
        /// <returns></returns>
        internal static ColumnCollection CreateFromDefinitionGreen(Table table, string columnDefinition, char delimiter)
        {
            ColumnCollection columns = new ColumnCollection(table);
            if (String.IsNullOrEmpty(columnDefinition)) return columns;
            string[] cds = columnDefinition.Split(delimiter);
            int cdsl = cds.Length;
            string[] items = new string[2];
            for (int i = 0; i < (cdsl - 1); i += 2)
            {
                Array.Copy(cds, i, items, 0, 2);
                Column column = Column.CreateFromDefinition(columns, items);
                if (column != null)
                    columns.Add(column);
            }
            return columns;
        }
        /// <summary>
        /// Vytvoří a vrátí kolekci sloupců z Asol notace (name,type,header,width,edit; name2,type2,header2;...).
        /// </summary>
        /// <param name="table"></param>
        /// <param name="columnDefinition"></param>
        /// <param name="delimiterC"></param>
        /// <param name="delimiterA"></param>
        /// <returns></returns>
        internal static ColumnCollection CreateFromDefinitionAsol(Table table, string columnDefinition, char delimiterC, char delimiterA)
        {
            ColumnCollection columns = new ColumnCollection(table);
            if (String.IsNullOrEmpty(columnDefinition)) return columns;
            string[] cds = columnDefinition.Split(delimiterC);
            foreach (string cd in cds)
            {
                string[] items = cd.Split(delimiterA);
                Column column = Column.CreateFromDefinition(columns, items);
                if (column != null)
                    columns.Add(column);
            }
            return columns;
        }
        /// <summary>
        /// Společná inicializace
        /// </summary>
        private void _Initialize()
        {
            this.ItemAddBefore += new CollectionEventHandler(ColumnCollection_ItemAddBefore);
            this.ItemRemoveAfter += new CollectionEventHandler(ColumnCollection_ItemRemoveAfter);
            this._ColumnNameIndex = new Dictionary<string, Column>();
        }
        /// <summary>
        /// Tvrdý index názvů sloupců
        /// </summary>
        private Dictionary<string, Column> _ColumnNameIndex;
        /// <summary>
        /// Tato tabulka (kolekce sloupců) nedovoluje duplicitu jména sloupce.
        /// Sice je možno zadat více prvků, které mají stejný název, 
        /// ale při přidávání do kolekce se název koriguje za pomoci indexu _ColumnNameIndex.
        /// </summary>
        protected override bool IgnoreNameDuplicity { get { return true; } }
        /// <summary>
        /// Tabulka, do níž kolekce sloupců patří (null, pokud ještě nepatří nikam)
        /// </summary>
        public Table Table { get; private set; }
        /// <summary>
        /// Vrátí jméno daného sloupce. Kolekce si udržuje index dle jména sloupce.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        protected override string GetName(Column item)
        {
            return item.ColumnName;
        }
        /// <summary>
        /// Před přidáním sloupce do kolekce je třeba do sloupce vložit referenci na this
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void ColumnCollection_ItemAddBefore(object sender, Collection<Column>.CollectionEventArgs args)
        {
            if (args.Item.Owner == null)
            {
                args.Item.SubmitTo(this);
                this._ColumnNameIndex.Add(args.Item.ColumnName, args.Item);
            }
        }
        /// <summary>
        /// Před odebráním sloupce z kolekce je třeba ze sloupce odebrat referenci na this
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void ColumnCollection_ItemRemoveAfter(object sender, Collection<Column>.CollectionEventArgs args)
        {
            if (this._ColumnNameIndex.ContainsKey(args.Item.ColumnName))
                this._ColumnNameIndex.Remove(args.Item.ColumnName);
            args.Item.Disconnect();
        }
        /// <summary>
        /// Vrátí true pokud this kolekce obsahuje prvek s daným názvem (název sloupce).
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        internal bool ContainsColumnName(string columnName)
        {
            return (this._ColumnNameIndex.ContainsKey(columnName));
        }
        /// <summary>
        /// Vepíše do this informace o tom, do které tabulky patří.
        /// </summary>
        /// <param name="table"></param>
        internal void SubmitTo(Table table)
        {
            if (table == null)
                throw new DevException("Kolekci sloupců nelze vložit do tabulky, která je null.");

            if (this.Table != null)
                throw new DevException("Kolekci sloupců nelze vložit do tabulky, neboť již v tabulce je.");

            this.Table = table;
        }
        #endregion
        #region Načítání ze streamu (DbDataReader), z tabulky (DataTable)
        /// <summary>
        /// Načte do sebe definice sloupců z daného readeru
        /// </summary>
        /// <param name="reader"></param>
        internal void FillFromReader(System.Data.Common.DbDataReader reader)
        {
            this.Table.Rows.Clear();
            this.Clear();
            this._ColumnNameIndex.Clear();

            var schema = reader.GetSchemaTable();
            int count = schema.Rows.Count;
            for (int index = 0; index < count; index++)
            {
                Column column = Column.CreateFromReader(schema.Rows[index], this);
                this.Add(column);
            }
        }
        /// <summary>
        /// Načte do sebe definice sloupců z dané tabulky DataTable
        /// </summary>
        /// <param name="reader"></param>
        internal void FillFromDataTable(System.Data.DataTable dataTable)
        {
            this.Table.Rows.Clear();
            this.Clear();

            Dictionary<string, int> columnNames = new Dictionary<string, int>();
            foreach (System.Data.DataColumn dataColumn in dataTable.Columns)
            {
                Column column = Column.CreateFromDataColumn(dataColumn, this);
                this.Add(column);
            }
        }
        #endregion
    }
    #endregion
    #region Column : Jeden sloupec tabulky
    /// <summary>
    /// Jeden sloupec tabulky
    /// </summary>
    public class Column
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        public Column()
        {
            this.ColumnIndex = -1;
            this.ColumnName = "";
            this.Owner = null;
            this.GridDataInit();
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        public Column(ColumnCollection owner)
        {
            this.ColumnIndex = (owner == null ? -1 : owner.Count);
            this.ColumnName = "";
            this.Owner = owner;
            this.GridDataInit();
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        public Column(string name)
        {
            this.ColumnIndex = -1;
            this.ColumnName = name;
            this.Owner = null;
            this.GridDataInit();
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        public Column(string name, Type dataType)
        {
            this.ColumnIndex = -1;
            this.ColumnName = name;
            this.DataType = ValueData.GetValueDataTypeFromType(dataType);
            this.Owner = null;
            this.GridDataInit();
        }
        /// <summary>
        /// Vytvoří a vrátí sloupec z dané definice.
        /// Definice je předána jako array stringů různé délky a obsahu, s fixním pořadím prvků:
        /// [0] = ColumnName; [1] = DataType (název typu). 
        /// Tyto první dva prvky jsou povinné, pokud pole je menší nebo tyto dva prvky jsou prázdné, vrací se NULL.
        /// [2] = titulek sloupce (Header)
        /// [3] = šířka sloupce: absolutní (int) nebo relativní (se suffixem %)
        /// [4] = editační styl, viz <see cref="Asol.Reporting.Support.FormatStyle.CreateFromDefinition()">FormatStyle.CreateFromDefinition()</see>
        /// </summary>
        /// <param name="columns"></param>
        /// <param name="items"></param>
        /// <returns></returns>
        internal static Column CreateFromDefinition(ColumnCollection columns, string[] items)
        {
            int itemLength = (items == null ? 0 : items.Length);
            if (itemLength < 2 || String.IsNullOrEmpty(items[0]) || String.IsNullOrEmpty(items[1])) return null;
            Column column = new Column(columns);

            // Povinné hodnoty = název sloupce a jeho datový typ:
            column.ColumnName = items[0];
            column.DataType = ValueData.GetValueDataTypeFromName(items[1]);

            if (itemLength >= 3 && String.IsNullOrEmpty(items[2]))
            {   // Text v záhlaví:
                column.GridHeaderText = Assistant.GetMessage(items[2]);
            }

            if (itemLength >= 4 && String.IsNullOrEmpty(items[3]))
            {   // Šířka relativní (50%) nebo absolutní v pixelech (200)
                if (items[3].EndsWith("%"))
                {
                    float weight =(Single)Convertor.StringToSingle(items[3].Replace("%", ""));
                    if (weight > 0f)
                        column.GridFillWeight = weight;
                }
                else
                {
                    int width = (Int32)Convertor.StringToInt32(items[3]);
                    if (width > 0)
                        column.GridColumnWidth = width;
                }
            }

            if (itemLength >= 5 && String.IsNullOrEmpty(items[4]))
            {   // Sestaví editační styl z dodané definice:
                column.GridFormat = FormatStyle.CreateFromDefinition(items[4]);
            }

            return column;
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Column[" + this.ColumnIndex.ToString() + "] " + this.ColumnName + "; " + this.DataType.ToString();
        }
        /// <summary>
        /// Index sloupce
        /// </summary>
        public int ColumnIndex { get; private set; }
        /// <summary>
        /// Kolekce sloupců, do níž sloupec patří (anebo null, pokud ještě nepatří nikam)
        /// </summary>
        public ColumnCollection Owner { get; private set; }
        /// <summary>
        /// Tabulka, do níž tento sloupec patří (null, pokud ještě nepatří nikam)
        /// </summary>
        public Table Table { get { return (this.Owner == null ? null : this.Owner.Table); } }
        /// <summary>
        /// Vepíše do this informace o tom, do které kolekce patří.
        /// Součástí zařazení do kolekce je i určení nekonfliktního jména: 
        /// Pokud by jméno this.ColumnName bylo duplicitní, pak vygeneruje nové se suffixem ~1 až ~999.
        /// Originál jména zálohuje do this.ColumnNameOriginal.
        /// Tato metoda nastavuje správnou hodnotu do this.ColumnIndex (na základě pořadí this objektu v kolekci).
        /// Tato metoda neprovádí fyzické přidání do kolekce Columns, pouze na tuto akci reaguje.
        /// </summary>
        /// <param name="collection"></param>
        internal void SubmitTo(ColumnCollection collection)
        {
            if (collection == null)
                throw new DevException("Sloupec nelze vložit do kolekce, která je null.");

            if (this.Owner != null)
                throw new DevException("Sloupec nelze vložit do kolekce, neboť již v kolekci je.");

            if (String.IsNullOrEmpty(this.ColumnNameOriginal))
                this.ColumnNameOriginal = this.ColumnName;

            string colName = this.ColumnName;
            string name = colName;                         // Výchozí jméno pro testování je to základní, načtené z readeru, bez suffixu
            for (int s = 1; s < 1000; s++)
            {
                if (!collection.ContainsColumnName(name)) break;     // Takové jméno nám nevadí => opustíme smyčku, v proměnné (name) máme neduplicitní jméno
                // Pokud je aktuální jméno už použito, pak otestujeme další jméno = výchozí jméno se suffixem 
                //   (postupně: name, name~1, name~2, name~3, name~4, name~5, ...):
                name = colName + "~" + s.ToString();
            }
            this.ColumnName = name;

            this.ColumnIndex = collection.Count;
            this.Owner = collection;
        }
        /// <summary>
        /// Odebere z this informace o tom, do které kolekce patřil.
        /// Jde o reverzní metodu k SubmitTo().
        /// Pokud při submitu bylo určeno jméno se suffixem (~1 až ~999), 
        /// pak při této akci Disconnect() se do ColumnName vrátí originál jména z this.ColumnNameOriginal.
        /// Tato metoda nastavuje do this.ColumnIndex hodnotu -1.
        /// Tato metoda neprovádí fyzické odebírání z kolekce Columns, pouze na tuto akci reaguje.
        /// </summary>
        internal void Disconnect()
        {
            if (!String.IsNullOrEmpty(this.ColumnNameOriginal))
                this.ColumnName = this.ColumnNameOriginal;
            this.ColumnNameOriginal = null;

            this.ColumnIndex = -1;
            this.Owner = null;
        }
        #endregion
        #region Public property popisující sloupec z pohledu zdroje dat (DataReader) + jejich naplnění z tabulky poskytnuté SQL serverem
        /// <summary>Název sloupce, zde je unikátní přes celou kolekci</summary>
        public String ColumnName { get; private set; }
        /// <summary></summary>
        public Int32 ColumnOrdinal { get; private set; }
        /// <summary></summary>
        public Int32 ColumnSize { get; private set; }
        /// <summary></summary>
        public Int16 NumericPrecision { get; private set; }
        /// <summary></summary>
        public Int16 NumericScale { get; private set; }
        /// <summary></summary>
        public Boolean IsUnique { get; private set; }
        /// <summary></summary>
        public Boolean IsKey { get; private set; }
        /// <summary></summary>
        public String BaseServerName { get; private set; }
        /// <summary></summary>
        public String BaseCatalogName { get; private set; }
        /// <summary></summary>
        public String BaseColumnName { get; private set; }
        /// <summary></summary>
        public String BaseSchemaName { get; private set; }
        /// <summary></summary>
        public String BaseTableName { get; private set; }
        /// <summary>Typ dat ve sloupci</summary>
        public ValueDataType DataType { get; private set; }
        /// <summary></summary>
        public Boolean AllowDBNull { get; private set; }
        /// <summary></summary>
        public Int32 ProviderType { get; private set; }
        /// <summary></summary>
        public Boolean IsAliased { get; private set; }
        /// <summary></summary>
        public Boolean IsExpression { get; private set; }
        /// <summary></summary>
        public Boolean IsIdentity { get; private set; }
        /// <summary></summary>
        public Boolean IsAutoIncrement { get; private set; }
        /// <summary></summary>
        public Boolean IsRowVersion { get; private set; }
        /// <summary></summary>
        public Boolean IsHidden { get; private set; }
        /// <summary></summary>
        public Boolean IsLong { get; private set; }
        /// <summary></summary>
        public Boolean IsReadOnly { get; private set; }
        /// <summary></summary>
        public Type ProviderSpecificDataType { get; private set; }
        /// <summary></summary>
        public String DataTypeName { get; private set; }
        /// <summary></summary>
        public String XmlSchemaCollectionDatabase { get; private set; }
        /// <summary></summary>
        public String XmlSchemaCollectionOwningSchema { get; private set; }
        /// <summary></summary>
        public String XmlSchemaCollectionName { get; private set; }
        /// <summary></summary>
        public String UdtAssemblyQualifiedName { get; private set; }
        /// <summary></summary>
        public Int32 NonVersionedProviderType { get; private set; }
        /// <summary></summary>
        public Boolean IsColumnSet { get; private set; }
        /// <summary>Jméno sloupce v DataReaderu, bez úprav. V property ColumnName je jméno unikátní (případné duplicity mají číselný suffix)</summary>
        public String ColumnNameOriginal { get; private set; }
        #region Načítání ze streamu (DbDataReader), z tabulky (DataTable)
        /// <summary>
        /// Vytvoří a vrátí definici sloupce z DataRow.
        /// Objekt System.Data.Common.DbDataReader (reader s daty z databáze) 
        /// dovoluje získat schema načítaných dat (pomocí metody GetSchemaTable(),
        /// jejíž jednotlivé řádky (DataRow) obsahují informace o každém sloupci datové tabulky.
        /// Z tohoto řádku (GetSchemaTable().Rows[]) je možno vytvořit objekt Column, který všechna tato data zachytí.
        /// K tomu právě slouží tato metoda CreateFromSchemaInfo)).
        /// </summary>
        /// <param name="row"></param>
        /// <param name="columnCollection">Vlastník sloupců (kolekce)</param>
        /// <returns></returns>
        public static Column CreateFromReader(System.Data.DataRow row, ColumnCollection columnCollection)
        {
            Column column = new Column(columnCollection);
            column.ColumnName = Table.Get<String>(row, "ColumnName");
            column.ColumnOrdinal = Table.Get<Int32>(row, "ColumnOrdinal");
            column.ColumnSize = Table.Get<Int32>(row, "ColumnSize");
            column.NumericPrecision = Table.Get<Int16>(row, "NumericPrecision");
            column.NumericScale = Table.Get<Int16>(row, "NumericScale");
            column.IsUnique = Table.Get<Boolean>(row, "IsUnique");
            column.IsKey = Table.Get<Boolean>(row, "IsKey");
            column.BaseServerName = Table.Get<String>(row, "BaseServerName");
            column.BaseCatalogName = Table.Get<String>(row, "BaseCatalogName");
            column.BaseColumnName = Table.Get<String>(row, "BaseColumnName");
            column.BaseSchemaName = Table.Get<String>(row, "BaseSchemaName");
            column.BaseTableName = Table.Get<String>(row, "BaseTableName");
            column.DataType = ValueData.GetValueDataTypeFromType(Table.Get<Type>(row, "DataType"));
            column.AllowDBNull = Table.Get<Boolean>(row, "AllowDBNull");
            column.ProviderType = Table.Get<Int32>(row, "ProviderType");
            column.IsAliased = Table.Get<Boolean>(row, "IsAliased");
            column.IsExpression = Table.Get<Boolean>(row, "IsExpression");
            column.IsIdentity = Table.Get<Boolean>(row, "IsIdentity");
            column.IsAutoIncrement = Table.Get<Boolean>(row, "IsAutoIncrement");
            column.IsRowVersion = Table.Get<Boolean>(row, "IsRowVersion");
            column.IsHidden = Table.Get<Boolean>(row, "IsHidden");
            column.IsLong = Table.Get<Boolean>(row, "IsLong");
            column.IsReadOnly = Table.Get<Boolean>(row, "IsReadOnly");
            column.ProviderSpecificDataType = Table.Get<Type>(row, "ProviderSpecificDataType");
            column.DataTypeName = Table.Get<String>(row, "DataTypeName");
            column.XmlSchemaCollectionDatabase = Table.Get<String>(row, "XmlSchemaCollectionDatabase");
            column.XmlSchemaCollectionOwningSchema = Table.Get<String>(row, "XmlSchemaCollectionOwningSchema");
            column.XmlSchemaCollectionName = Table.Get<String>(row, "XmlSchemaCollectionName");
            column.UdtAssemblyQualifiedName = Table.Get<String>(row, "UdtAssemblyQualifiedName");
            column.NonVersionedProviderType = Table.Get<Int32>(row, "NonVersionedProviderType");
            column.IsColumnSet = Table.Get<Boolean>(row, "IsColumnSet");

            column.GridDataInit();
            return column;
        }
        /// <summary>
        /// Vytvoří a vrátí definici sloupce z DataRow.
        /// Objekt System.Data.Common.DbDataReader (reader s daty z databáze) 
        /// dovoluje získat schema načítaných dat (pomocí metody GetSchemaTable(),
        /// jejíž jednotlivé řádky (DataRow) obsahují informace o každém sloupci datové tabulky.
        /// Z tohoto řádku (GetSchemaTable().Rows[]) je možno vytvořit objekt Column, který všechna tato data zachytí.
        /// K tomu právě slouží tato metoda CreateFromSchemaInfo)).
        /// </summary>
        /// <param name="row"></param>
        /// <returns></returns>
        public static Column CreateFromDataColumn(System.Data.DataColumn dataColumn, ColumnCollection columnCollection)
        {
            Column column = new Column(columnCollection);
            column.ColumnName = dataColumn.ColumnName;
            column.ColumnOrdinal = dataColumn.Ordinal;
            column.ColumnSize = dataColumn.MaxLength;
            column.NumericPrecision = 0;
            column.NumericScale = 0;
            column.IsUnique = dataColumn.Unique;
            column.IsKey = false;
            column.BaseServerName = "";
            column.BaseCatalogName = "";
            column.BaseColumnName = "";
            column.BaseSchemaName = "";
            column.BaseTableName = "";
            column.DataType = ValueData.GetValueDataTypeFromType(dataColumn.DataType);
            column.AllowDBNull = dataColumn.AllowDBNull;
            column.ProviderType = 0;
            column.IsAliased = false;
            column.IsExpression = !String.IsNullOrEmpty(dataColumn.Expression);
            column.IsIdentity = dataColumn.AutoIncrement;
            column.IsAutoIncrement = dataColumn.AutoIncrement;
            column.IsRowVersion = false;
            column.IsHidden = false;
            column.IsLong = false;
            column.IsReadOnly = dataColumn.ReadOnly;
            column.ProviderSpecificDataType = null;
            column.DataTypeName = dataColumn.DataType.NsName();
            column.XmlSchemaCollectionDatabase = null;
            column.XmlSchemaCollectionOwningSchema = null;
            column.XmlSchemaCollectionName = null;
            column.UdtAssemblyQualifiedName = null;
            column.NonVersionedProviderType = 0;
            column.IsColumnSet = false;

            column.GridDataInit();
            return column;
        }
        #endregion
        #endregion
        #region Public property podporující vizualizaci dat v tomto sloupci. Je nutno je plnit programově, nepocházejí z databáze
        /// <summary>Viditelnost sloupce</summary>
        public bool GridVisible { get { return this._GridVisible; } set { this._GridVisible = value; this.NotifyGridColumnChange("GridVisible"); } } private bool _GridVisible;
        /// <summary>Text v záhlaví</summary>
        public string GridHeaderText { get { return this._GridHeaderText; } set { this._GridHeaderText = value; this.NotifyGridColumnChange("GridHeaderText"); } } private string _GridHeaderText;
        /// <summary>Druh vizuálního objektu ve sloupci (text, combobox, obrázek)</summary>
        public CellType GridCellType { get { return this._GridCellType; } set { this._GridCellType = value; this.NotifyGridColumnChange("GridCellType"); } } private CellType _GridCellType;
        /// <summary>Šířka v pixelech, výchozí</summary>
        public int? GridColumnWidth { get { return this._GridColumnWidth; } set { this._GridColumnWidth = value; this.NotifyGridColumnChange("GridColumnWidth"); } } private int? _GridColumnWidth;
        /// <summary>Automatická šířka, váha proti ostatním</summary>
        public float? GridFillWeight { get { return this._GridFillWeight; } set { this._GridFillWeight = value; this.NotifyGridColumnChange("GridFillWeight"); } } private float? _GridFillWeight;
        /// <summary>Režim změny šířky sloupce</summary>
        public System.Windows.Forms.DataGridViewAutoSizeColumnMode GridAutoSizeMode { get { return this._GridAutoSizeMode; } set { this._GridAutoSizeMode = value; this.NotifyGridColumnChange("GridAutoSizeMode"); } } private System.Windows.Forms.DataGridViewAutoSizeColumnMode _GridAutoSizeMode;
        /// <summary>Tooltip k záhlaví</summary>
        public string GridToolTipText { get { return this._GridToolTipText; } set { this._GridToolTipText = value; this.NotifyGridColumnChange("GridToolTipText"); } } private string _GridToolTipText;
        /// <summary></summary>
        public bool GridAsRelation { get { return this._GridAsRelation; } set { this._GridAsRelation = value; this.NotifyGridColumnChange("GridAsRelation"); } } private bool _GridAsRelation;
        /// <summary></summary>
        public bool GridShowOnlyDifferentValues { get { return this._GridShowOnlyDifferentValues; } set { this._GridShowOnlyDifferentValues = value; this.NotifyGridColumnChange("GridShowOnlyDifferentValues"); } } private bool _GridShowOnlyDifferentValues;
        /// <summary></summary>
        public int? GridDifferentValuesDetectInColumnIndex { get { return this._GridDifferentValuesDetectInColumnIndex; } set { this._GridDifferentValuesDetectInColumnIndex = value; this.NotifyGridColumnChange("GridDifferentValuesDetectInColumnIndex"); } } private int? _GridDifferentValuesDetectInColumnIndex;
        /// <summary>Základní grafický styl pro sloupec (barvy pro různé stavy) = použije se tehdy, když buňka ani řádek nemají svůj vlastní styl</summary>
        public Components.GridStyle GridStyle { get { return this._GridStyle; } set { this._GridStyle = value; this.NotifyGridColumnChange("GridStyle"); } } private Components.GridStyle _GridStyle;
        /// <summary>Objekt zajišťující převod hodnoty z tohoto sloupce do stringové reprezentace. Pokud je zde null, pak se použije objekt Assistent.DefaultFormatStyle.</summary>
        public FormatStyle GridFormat { get { return this._GridFormat; } set { this._GridFormat = value; this.NotifyGridColumnChange("GridFormatStyle"); } } private FormatStyle _GridFormat;
        /// <summary>
        /// Příznak, že data sloupce mohou být editovatelná. Default = true.
        /// </summary>
        public bool Enabled { get { return !this._Disabled; } set { this._Disabled = !value; } } private bool _Disabled = false;
        /// <summary>
        /// Inicializace property typu Grid
        /// </summary>
        protected void GridDataInit()
        {
            this._GridVisible = true;
            this._GridHeaderText = this.ColumnNameOriginal;
            this._GridCellType = CellType.TextBox;
            this._GridColumnWidth = 150;
            this._GridFillWeight = 100f;
            this._GridAutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None;
            this._GridToolTipText = "";
            this._GridAsRelation = false;
            this._GridShowOnlyDifferentValues = false;
            this._GridDifferentValuesDetectInColumnIndex = null;
            this._GridStyle = null;
            this._GridFormat = null;
            this._Disabled = false;
        }
        /// <summary>
        /// Zajistí odeslání notifikace o změně hodnoty Column.Grid* do Gridu, navázaného na tabulce.
        /// </summary>
        /// <param name="propertyName"></param>
        protected void NotifyGridColumnChange(string propertyName)
        {
            if (this.Table != null)
                this.Table.NotifyGridColumnChange(this, propertyName);
        }
        #endregion
    }
    #endregion
    #endregion
    #region RowCollection + Row
    #region RowCollection : Kolekce řádků tabulky
    /// <summary>
    /// Kolekce řádků tabulky
    /// </summary>
    public class RowCollection : Collection<Row>
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        public RowCollection()
            : base()
        {
            this.Table = null;
            this._Initialize();
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="owner"></param>
        internal RowCollection(Table owner)
            : base()
        {
            this.Table = owner;
            this._Initialize();
        }
        /// <summary>
        /// Společná inicializace
        /// </summary>
        private void _Initialize()
        {
            this.ItemAddBefore += new CollectionEventHandler(RowCollection_ItemAddBefore);
            this.ItemRemoveAfter += new CollectionEventHandler(RowCollection_ItemRemoveAfter);
        }
        /// <summary>
        /// Tabulka, do níž kolekce sloupců patří (null, pokud ještě nepatří nikam)
        /// </summary>
        public Table Table { get; private set; }
        /// <summary>
        /// Vrací název pro danou položku. Řádky název nemají.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        protected override string GetName(Row item)
        {
            return null;
        }
        /// <summary>
        /// Před přidáním řádku do kolekce je třeba do řádku vložit referenci na this
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void RowCollection_ItemAddBefore(object sender, Collection<Row>.CollectionEventArgs args)
        {
            if (args.Item.Owner == null)
                args.Item.SubmitTo(this);
        }
        /// <summary>
        /// Před odebráním řádku z kolekce je třeba z řádku odebrat referenci na this
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void RowCollection_ItemRemoveAfter(object sender, Collection<Row>.CollectionEventArgs args)
        {
            args.Item.Disconnect();
        }
        /// <summary>
        /// Vepíše do this informace o tom, do které tabulky patří.
        /// </summary>
        /// <param name="table"></param>
        internal void SubmitTo(Table table)
        {
            if (table == null)
                throw new DevException("Kolekci řádků nelze vložit do tabulky, která je null.");

            if (this.Table != null)
                throw new DevException("Kolekci řádků nelze vložit do tabulky, neboť již v tabulce je.");

            this.Table = table;
        }
        #endregion
        #region Načítání ze streamu (DbDataReader), z tabulky (DataTable)
        /// <summary>
        /// Načte do sebe obsah řádků z daného readeru
        /// </summary>
        /// <param name="reader"></param>
        internal void FillFromReader(System.Data.Common.DbDataReader reader)
        {
            this.Clear();
            if (!reader.HasRows) return;

            while (reader.Read())
            {
                Row row = Row.CreateFromReader(reader, this);
                this.Add(row);
            }

        }
        /// <summary>
        /// Načte do sebe obsah řádků z dané tabulky DataTable
        /// </summary>
        /// <param name="reader"></param>
        internal void FillFromDataTable(System.Data.DataTable dataTable)
        {
            this.Clear();
            if (dataTable.Rows.Count == 0) return;

            foreach (System.Data.DataRow dataRow in dataTable.Rows)
            {
                Row row = Row.CreateFromDataRow(dataRow);
                this.Add(row);
            }
        }
        #endregion
    }
    #endregion
    #region Row : Jeden řádek s daty
    /// <summary>
    /// Jeden řádek s daty
    /// </summary>
    public class Row
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        public Row()
        {
            this.RowIndex = -1;
            this.CurrentIndex = -1;
            this.Owner = null;
            this.Cells = new object[0];
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        public Row(RowCollection collection)
        {
            this.RowIndex = collection.Count;
            this.CurrentIndex = collection.Count;
            this.Owner = collection;
            int cellCount = (collection == null || collection.Table == null || collection.Table.Columns == null ? 0 : collection.Table.Columns.Count);
            this.Cells = new object[cellCount];
        }
        /// <summary>
        /// Index řádku v okamžiku načtení z databáze
        /// </summary>
        public int RowIndex { get; private set; }
        /// <summary>
        /// Index řádku v aktuálním okamžiku (tedy po setřídění)
        /// </summary>
        public int CurrentIndex { get; private set; }
        /// <summary>
        /// Kolekce řádků, do níž sloupec patří (anebo null, pokud ještě nepatří nikam)
        /// </summary>
        public RowCollection Owner { get; private set; }
        /// <summary>
        /// Tabulka, do níž tento řádek patří (null, pokud ještě nepatří nikam)
        /// </summary>
        public Table Table { get { return (this.Owner == null ? null : this.Owner.Table); } }
        /// <summary>
        /// Kolekce sloupců, pro které tento řádek obsahuje data (null, pokud ještě nepatří nikam)
        /// </summary>
        public ColumnCollection Columns { get { return (this.Owner == null ? null : this.Owner.Table.Columns); } }
        /// <summary>
        /// Vepíše do this informace o tom, do které kolekce patří.
        /// </summary>
        /// <param name="collection"></param>
        internal void SubmitTo(RowCollection collection)
        {
            if (collection == null)
                throw new DevException("Řádek nelze vložit do kolekce, která je null.");

            if (this.Owner != null)
                throw new DevException("Řádek nelze vložit do kolekce, neboť již v kolekci je.");

            this.RowIndex = collection.Count;
            this.CurrentIndex = collection.Count;
            this.Owner = collection;

            object[] values = this.Cells;                  // Ponechám si na chvíli referenci na pole s daty
            int columnCount = (collection.Table != null && collection.Table.Columns != null ? collection.Table.Columns.Count : values.Length);
            this.Cells = new object[columnCount];          // Do this vytvořím nové pole s daty, v délce dané počtem sloucpů
            this.FillCellsFrom(values);                    // Do nového pole this.Cells přenesu data z buněk z původního pole, v potřebné délce, s kontrolou datových typů
        }
        /// <summary>
        /// Odebere z this informace o tom, do které kolekce patřil.
        /// Jde o reverzní metodu k SubmitTo().
        /// </summary>
        internal void Disconnect()
        {
            this.RowIndex = -1;
            this.CurrentIndex = -1;
            this.Owner = null;
        }
        #endregion
        #region Obsah řádku
        /// <summary>
        /// Obsah buněk, fyzicky načtená data
        /// </summary>
        public object[] Cells { get; private set; }
        /// <summary>
        /// Počet buněk v řádku, odpovídá počtu sloupců v tabulce
        /// </summary>
        public int CellCount { get { return (this.Cells == null ? -1 : this.Cells.Length); } }
        /// <summary>
        /// Přístup k datům řádku pomocí jména sloupce
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public ValueData this[string columnName]
        {
            get { return this[this.Table.Columns[columnName]]; }
            set { this[this.Table.Columns[columnName]] = value; }
        }
        /// <summary>
        /// Přístup k datům řádku pomocí integer indexu
        /// </summary>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public ValueData this[int columnIndex]
        {
            get { return this[this.Table.Columns[columnIndex]]; }
            set { this[this.Table.Columns[columnIndex]] = value; }
        }
        /// <summary>
        /// Přístup k datům řádku pomocí objektu sloupce
        /// </summary>
        /// <param name="column"></param>
        /// <returns></returns>
        public ValueData this[Column column]
        {
            get
            {
                if (!Object.ReferenceEquals(column.Table, this.Table))
                    throw new DevException("NotReferenceEquals: V tabulce X nelze pracovat s daty sloupce Y.");
                return new ValueData(this.Cells[column.ColumnIndex]);
            }
            set
            {
                if (!Object.ReferenceEquals(column.Table, this.Table))
                    throw new DevException("NotReferenceEquals: V tabulce X nelze pracovat s daty sloupce Y.");
                if (value.IsEmpty)
                    this.Cells[column.ColumnIndex] = null;
                else if (value.Type == column.DataType)
                    this.Cells[column.ColumnIndex] = value.NativeValue;
                else
                    throw new DevException("Hodnota typu %0 nemůže být uložena do sloupce %1, který je typu %2.", value.Type.ToString(), column.ColumnName, column.DataType.ToString());
            }
        }
        ///// <summary>
        ///// Vrátí typovou hodnotu z aktuálního řádku a daného sloupce. Namísto DbNull vrací default daného typu.
        ///// </summary>
        ///// <typeparam name="T"></typeparam>
        ///// <param name="row"></param>
        ///// <param name="columnName"></param>
        ///// <returns></returns>
        //public T Get<T>(string columnName)
        //{
        //    if (!this.Table.Columns.ContainsName(columnName)) return default(T);
        //    return this.Get<T>(this.Table.Columns[columnName]);
        //}
        ///// <summary>
        ///// Vrátí typovou hodnotu z aktuálního řádku a daného sloupce. Namísto DbNull vrací default daného typu.
        ///// </summary>
        ///// <typeparam name="T"></typeparam>
        ///// <param name="row"></param>
        ///// <param name="columnIndex"></param>
        ///// <returns></returns>
        //public T Get<T>(int columnIndex)
        //{
        //    if (!this.Table.Columns.ContainsIndex(columnIndex)) return default(T);
        //    return this.Get<T>(this.Table.Columns[columnIndex]);
        //}
        ///// <summary>
        ///// Vrátí typovou hodnotu z aktuálního řádku a daného sloupce. Namísto DbNull vrací default daného typu.
        ///// </summary>
        ///// <typeparam name="T"></typeparam>
        ///// <param name="row"></param>
        ///// <param name="column"></param>
        ///// <returns></returns>
        //public T Get<T>(Column column)
        //{
        //    if (column == null) return default(T);
        //    if (!Object.ReferenceEquals(column.Table, this.Table))
        //        throw new ReportException("NotReferenceEquals: V tabulce X nelze pracovat s daty sloupce Y.");
        //    object value = this[column];
        //    if (value == null) return default(T);
        //    if (value is T)
        //        return (T)value;
        //    return default(T);
        //}
        /// <summary>
        /// Do řádku vloží data z dodaného pole.
        /// Pokud pole obsahuje méně prvků než je třeba (počet sloupců), pak zbývající budou NULL.
        /// Pokud pole obsahuje více prvků než je třeba (počet sloupců), pak nadbytečné budou ignorovány.
        /// Kontrolují se datové typy, zda konkrétní typ lze do odpovídajícího sloupce vložit. Pokud konkrétní objekt není typu daného sloupcem (ani jeho potomken), pak se do buňky nevloží, bude tam NULL.
        /// </summary>
        /// <param name="values"></param>
        public void FillCellsFrom(object[] values)
        {
            int dataCnt  = (values == null ? 0 : values.Length);
            int colCnt = 0;
            int scanCnt = 0;
            if (this.Table != null && this.Table.Columns != null)
            {   // a) standardně = pokud řádek patří do tabulky, a známe sloupce:
                colCnt = this.Table.Columns.Count;
                scanCnt = colCnt;
            }
            else
            {   // b) před zařazením řádku do tabulky (když je řádek izolovaný):
                colCnt = 0;
                scanCnt = dataCnt;
                this.Cells = new object[dataCnt];
            }

            for (int i = 0; i < scanCnt; i++)
            {
                object value = (i < dataCnt ? values[i] : null);
                if (value == null)                  // nejsou vstupní data (jsou null nebo vůbec nejsou)
                    this.Cells[i] = null;
                else if (colCnt == 0)               // izolovaný řádek
                    this.Cells[i] = value;
                else if (ValueData.GetValueDataTypeFromType(value.GetType()) == this.Table.Columns[i].DataType)
                    this.Cells[i] = value;          // data odpovídají typu sloupce
                else
                    this.Cells[i] = null;
            }
        }
        #endregion
        #region Načítání ze streamu (DbDataReader), z tabulky (DataTable)
        /// <summary>
        /// Vytvoří nový řádek z dat v readeru
        /// </summary>
        /// <param name="reader">Reader s daty</param>
        /// <returns></returns>
        internal static Row CreateFromReader(System.Data.Common.DbDataReader reader)
        {
            return _CreateFromReader(reader, null);
        }
        /// <summary>
        /// Vytvoří nový řádek z dat v readeru
        /// </summary>
        /// <param name="reader">Reader s daty</param>
        /// <param name="rowCollection">Kolekce řádků. Řádek se do ní nevkládá.</param>
        /// <returns></returns>
        internal static Row CreateFromReader(System.Data.Common.DbDataReader reader, RowCollection rowCollection)
        {
            return _CreateFromReader(reader, rowCollection);
        }
        /// <summary>
        /// Vytvoří nový řádek z dat v readeru
        /// </summary>
        /// <param name="reader">Reader s daty</param>
        /// <param name="rowCollection">Kolekce řádků. Řádek se do ní nevkládá.</param>
        /// <returns></returns>
        private static Row _CreateFromReader(System.Data.Common.DbDataReader reader, RowCollection rowCollection)
        {
            Row row = new Row(rowCollection);
            int count = reader.FieldCount;
            row.Cells = new object[count];
            for (int index = 0; index < count; index++)
            {
                if (!reader.IsDBNull(index))
                    row.Cells[index] = reader[index];
            }
            return row;
        }
        /// <summary>
        /// Vytvoří nový řádek z dat v DataTable.Rows
        /// </summary>
        /// <param name="dataRow">Řádek s daty</param>
        /// <returns></returns>
        internal static Row CreateFromDataRow(System.Data.DataRow dataRow)
        {
            return _CreateFromDataRow(dataRow, null);
        }
        /// <summary>
        /// Vytvoří nový řádek z dat v DataTable.Rows
        /// </summary>
        /// <param name="dataRow">Řádek s daty</param>
        /// <param name="rowCollection">Kolekce řádků. Řádek se do ní nevkládá.</param>
        /// <returns></returns>
        internal static Row CreateFromDataRow(System.Data.DataRow dataRow, RowCollection rowCollection)
        {
            return _CreateFromDataRow(dataRow, rowCollection);
        }
        /// <summary>
        /// Vytvoří nový řádek z dat v DataTable.Rows
        /// </summary>
        /// <param name="dataRow">Řádek s daty</param>
        /// <param name="rowCollection">Kolekce řádků. Řádek se do ní nevkládá.</param>
        /// <returns></returns>
        private static Row _CreateFromDataRow(System.Data.DataRow dataRow, RowCollection rowCollection)
        {
            Row row = new Row(rowCollection);
            object[] itemArray = dataRow.ItemArray;
            int count = itemArray.Length;
            row.Cells = new object[count];
            for (int index = 0; index < count; index++)
            {
                if (!dataRow.IsNull(index))
                    row.Cells[index] = itemArray[index];
            }
            return row;
        }
        #endregion
        #region Další vlastnosti řádku nad rámec dat, pouze OnDemand (načtením řádku z databáze se nezakládají, jsou null)
        /// <summary>
        /// Vrátí tooltip pro konkrétní buňku. Null = není určeno.
        /// </summary>
        /// <param name="cellIndex"></param>
        /// <returns></returns>
        public string GetCellTooltip(int cellIndex)
        {
            if (this.Tooltips == null) return null;
            if (cellIndex >= 0 && cellIndex < this.Tooltips.Length) return this.Tooltips[cellIndex];
            return null;
        }
        /// <summary>
        /// Nastaví tooltip pro konkrétní buňku.
        /// </summary>
        /// <param name="cellIndex"></param>
        /// <param name="tooltip"></param>
        public void SetCellTooltip(int cellIndex, string tooltip)
        {
            if (this.Tooltips == null && String.IsNullOrEmpty(tooltip)) return;
            if (cellIndex < 0 || cellIndex >= this.CellCount) return;

            if (this.Tooltips == null || this.Tooltips.Length != this.CellCount)
                this.Tooltips = new string[this.CellCount];
            this.Tooltips[cellIndex] = tooltip;
        }
        /// <summary>
        /// Pole textů tooltipů
        /// </summary>
        protected string[] Tooltips;
        /// <summary>
        /// Vrátí GridStyle pro konkrétní buňku. Null = není určeno.
        /// </summary>
        /// <param name="cellIndex"></param>
        /// <returns></returns>
        public Components.GridStyle GetCellGridStyle(int cellIndex)
        {
            if (this.GridStyles == null) return null;
            if (cellIndex >= 0 && cellIndex < this.Tooltips.Length) return this.GridStyles[cellIndex];
            return null;
        }
        /// <summary>
        /// Nastaví GridStyle pro konkrétní buňku.
        /// </summary>
        /// <param name="cellIndex"></param>
        /// <param name="style"></param>
        public void SetCellGridStyle(int cellIndex, Components.GridStyle style)
        {
            if (this.GridStyles == null && style == null) return;
            if (cellIndex < 0 || cellIndex >= this.CellCount) return;

            if (this.GridStyles == null || this.GridStyles.Length != this.CellCount)
                this.GridStyles = new Components.GridStyle[this.CellCount];
            this.GridStyles[cellIndex] = style;
        }
        /// <summary>
        /// Pole stylů jednotlivých buněk, default = null
        /// </summary>
        protected Components.GridStyle[] GridStyles;
        /// <summary>
        /// Základní grafický styl pro řádek = pro všechny jeho buňky, které nemají svůj vlastní styl
        /// </summary>
        public Components.GridStyle GridStyle { get; set; }
        /// <summary>
        /// Vrátí jméno formátovacího stylu pro formátování obsahu konkrétní buňky. Null = není určeno.
        /// </summary>
        /// <param name="cellIndex"></param>
        /// <returns></returns>
        public FormatStyle GetCellFormatStyle(int cellIndex)
        {
            if (this.GridFormatStyles == null) return null;
            if (cellIndex >= 0 && cellIndex < this.GridFormatStyles.Length) return this.GridFormatStyles[cellIndex];
            return null;
        }
        /// <summary>
        /// Nastaví objekt formátovacího stylu = formátování obsahu pro konkrétní buňku.
        /// </summary>
        /// <param name="cellIndex"></param>
        /// <param name="formatStyle"></param>
        public void SetCellFormatStyle(int cellIndex, FormatStyle formatStyle)
        {
            if (this.GridFormatStyles == null && formatStyle == null) return;
            if (cellIndex < 0 || cellIndex >= this.CellCount) return;

            if (this.GridFormatStyles == null || this.GridFormatStyles.Length != this.CellCount)
                this.GridFormatStyles = new FormatStyle[this.CellCount];
            this.GridFormatStyles[cellIndex] = formatStyle;
        }
        /// <summary>
        /// Obecný styl formátování hodnot z konkrétní buňky na text.
        /// </summary>
        protected FormatStyle[] GridFormatStyles;
        /// <summary>
        /// Vrátí příznak Enabled pro konkrétní buňku. Default = true
        /// </summary>
        /// <param name="cellIndex"></param>
        /// <returns></returns>
        public bool GetCellEnabled(int cellIndex)
        {
            if (this.Disableds == null) return true;
            if (cellIndex >= 0 && cellIndex < this.Disableds.Length) return !this.Disableds[cellIndex];
            return true;
        }
        /// <summary>
        /// Nastaví příznak Enabled pro konkrétní buňku.
        /// </summary>
        /// <param name="cellIndex"></param>
        /// <param name="enabled"></param>
        public void SetCellEnabled(int cellIndex, bool enabled)
        {
            if (this.Disableds == null && enabled) return;
            if (cellIndex < 0 || cellIndex >= this.CellCount) return;

            if (this.Disableds == null || this.Disableds.Length != this.CellCount)
                this.Disableds = new bool[this.CellCount];
            this.Disableds[cellIndex] = !enabled;
        }
        /// <summary>
        /// Soupis hodnot Disabled pro jednotlivé columny
        /// </summary>
        protected bool[] Disableds;
        /// <summary>
        /// Příznak, že data řádku mohou být editovatelná. Default = true.
        /// </summary>
        public bool Enabled { get { return !this._Disabled; } set { this._Disabled = !value; } } private bool _Disabled = false;
        #endregion
    }
    #endregion
    #endregion
    #region class TableArgs a její potomstvo - argumenty pro eventy. Odpovídající delegáti.
    /// <summary>
    /// Základní třída pro argumenty
    /// </summary>
    public class TableArgs : EventArgs
    { }
    /// <summary>
    /// Třída pro událost Změna hodnoty
    /// </summary>
    public class TableValueChangeArgs : TableArgs
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <param name="valueOld"></param>
        /// <param name="valueNew"></param>
        public TableValueChangeArgs(int rowIndex, int columnIndex, object valueOld, object valueNew)
        {
            this.RowIndex = rowIndex;
            this.ColumnIndex = columnIndex;
            this.ValueOld = valueOld;
            this.ValueNew = valueNew;
        }
        /// <summary>
        /// Index řádku, kde došlo ke změně
        /// </summary>
        public int RowIndex { get; private set; }
        /// <summary>
        /// Index sloupce, kde došlo ke změně
        /// </summary>
        public int ColumnIndex { get; private set; }
        /// <summary>
        /// Původní hodnota před změnou
        /// </summary>
        public object ValueOld { get; private set; }
        /// <summary>
        /// Nová hodnota po změně
        /// </summary>
        public object ValueNew { get; private set; }
    }
    /// <summary>
    /// Předpis handleru pro události o provedené změně
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public delegate void TableValueChangeHandler(object sender, TableValueChangeArgs args);
    /// <summary>
    /// Třída pro událost Změna hodnoty
    /// </summary>
    public class TableValueChangingArgs : TableValueChangeArgs
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <param name="valueOld"></param>
        /// <param name="valueNew"></param>
        public TableValueChangingArgs(int rowIndex, int columnIndex, object valueOld, object valueNew)
            : base(rowIndex, columnIndex, valueOld, valueNew)
        {
            this.ValueNew = valueNew;
            this.Cancel = false;
        }
        /// <summary>
        /// Nová hodnota, která se bude do prvku vkládat.
        /// V události Changing je možno ji změnit.
        /// </summary>
        public new object ValueNew { get; set; }
        /// <summary>
        /// Pokud má dojít k odmítnutí, nastaví se true.
        /// </summary>
        public bool Cancel { get; set; }
    }
    /// <summary>
    /// Předpis handleru pro události o probíhající změně
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public delegate void TableValueChangingHandler(object sender, TableValueChangingArgs args);
    #endregion
    #region Enumy
    /// <summary>
    /// Požadovaný typ vizuální buňky
    /// </summary>
    public enum CellType
    {
        /// <summary>Nic</summary>
        None = 0,
        /// <summary>Text</summary>
        TextBox,
        /// <summary>Combo</summary>
        ComboBox,
        /// <summary>Obrázek</summary>
        Image
    }
    /// <summary>
    /// Režim slučování buněk
    /// </summary>
    public enum CellJoinMode
    {
        /// <summary>
        /// V tomto sloupci se buňky neslučují
        /// </summary>
        None = 0,
        /// <summary>
        /// V tomto sloupci se buňky slučují, ale tato buňka je samotná
        /// </summary>
        SingleCell,
        /// <summary>
        /// První buňka v řadě
        /// </summary>
        FirstCell,
        /// <summary>
        /// Vnitřní buňka (ani první, ani poslední)
        /// </summary>
        InnerCell,
        /// <summary>
        /// Poslední buňka v řadě
        /// </summary>
        LastCell
    }
    #endregion
}
